package com.mybatis.demo.plugin;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

/**
 * @author zp
 * @version 1.0
 * @description: 分页拦截器
 * 拦截StatementHandler 的prepare方法 增强预处理SQL
 * @date 2022/11/24 20:04
 */
@Slf4j
@Intercepts(@Signature(type = StatementHandler.class,
        method = "prepare", args = {Connection.class, Integer.class}))
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 设置总行数 select count(0) from (原有的SQL)
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        SqlContext sqlContext = SqlContext.getSqlContext();
        Map<String, Integer> map = null;
        // 获取前缀 判断语句类型
        String pre = sql.substring(0, 6);
        if (SqlContext.SELECT.equalsIgnoreCase(pre)) {
            map = sqlContext.getSelectMap();
        } else if (SqlContext.DELETE.equalsIgnoreCase(pre)) {
            map = sqlContext.getDeleteMap();
        } else if (SqlContext.UPDATE.equalsIgnoreCase(pre)) {
            map = sqlContext.getUpdateMap();
        } else if (SqlContext.INSERT.equalsIgnoreCase(pre)) {
            map = sqlContext.getInsertMap();
        }
        // 调整次数 统计语句调用的次数
        if (map != null) {
            int i = map.get(sql) == null ? 1 : map.get(sql) + 1;
            map.put(sql, i);
        }

        Page page = null;
        if (boundSql.getParameterObject() instanceof Page) {
            page = (Page) boundSql.getParameterObject();
        } else if (boundSql.getParameterObject() instanceof Map) {
            Map mapPage = (Map) boundSql.getParameterObject();
            page = (Page) mapPage.values().stream().filter(v -> v instanceof Page).findFirst().orElse(null);
        }

        if (page == null) {
            return invocation.proceed();
        }
        int i = selectCount(invocation);
        page.setTotal(i);
        log.info("获取的总行数 [{}]", i);

        // 修改原sql 增加分页逻辑 select * from offset 0,limit 50
        String newSql = String.format("%s limit %s offset %s", sql, page.getSize(), page.getOffset());
        SystemMetaObject.forObject(boundSql).setValue("sql", newSql);

        return invocation.proceed();
    }

    private int selectCount(Invocation invocation) throws SQLException {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        log.info("被设置的原来的SQL [{}]", sql);
        // 拼接SQL
        String newSql = String.format("select count(*) from ( %s ) as _page", sql);
        log.info("被设置的之后的SQL [{}]", newSql);
        Connection connection = (Connection) invocation.getArgs()[0];
        // 设置参数绑定
        PreparedStatement preparedStatement = connection.prepareStatement(newSql);
        statementHandler.getParameterHandler().setParameters(preparedStatement);

        ResultSet resultSet = preparedStatement.executeQuery();
        int count = 0;
        if (resultSet.next()) {
            count = resultSet.getInt(1);
        }
        return count;
    }

    /**
     * @param target
     * @return Object
     * @description: Plugin.wrap(target, this);这个方法是使用动态代理的方式 实现方法的拦截和增强
     * 也可以使用静态内部类的方式实现增强
     * 只需要实现四大组件的接口 写一个静态内部类 完成方法的增强和逻辑处理
     * Plugin.wrap(target,this)方法是必须使用Intercepts注解完成的
     * 因为底层是根据注解类 查询到需要代理的对象 完成动态代理
     * @author zp
     * @date: 2022/11/26 10:32
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
